/**
 * Unified logging system for Vision Analyzer
 * Provides cross-platform logging with rotation and configurable levels
 */

const fs = require('fs');
const fsPromises = require('fs').promises;
const path = require('path');
const { getLogDir, getLogPath, ensureDirectory } = require('../utils/platform');
const { ErrorFactory } = require('../utils/errors');

/**
 * Log levels in order of severity
 */
const LOG_LEVELS = {
  error: 0,
  warn: 1,
  info: 2,
  debug: 3
};

/**
 * Default log configuration
 */
const DEFAULT_CONFIG = {
  level: 'info',
  retention_days: 30,
  max_file_size: '10MB',
  file_enabled: true,
  console_enabled: true,
  timestamp_format: 'ISO', // 'ISO' or 'unix'
  colors: true
};

/**
 * Logger class for Vision Analyzer
 */
class Logger {
  constructor(config = {}) {
    this.config = { ...DEFAULT_CONFIG, ...config };
    this.logDir = getLogDir();
    this.logFile = getLogPath();
    this.currentLogLevel = LOG_LEVELS[this.config.level] !== undefined ?
      LOG_LEVELS[this.config.level] : LOG_LEVELS.info;

    // Ensure log directory exists
    if (this.config.file_enabled && !ensureDirectory(this.logDir)) {
      console.warn('Failed to create log directory, file logging disabled');
      this.config.file_enabled = false;
    }

    // ANSI color codes for console output
    this.colors = {
      error: '\x1b[31m', // red
      warn: '\x1b[33m',  // yellow
      info: '\x1b[36m',  // cyan
      debug: '\x1b[37m', // white
      reset: '\x1b[0m'   // reset
    };
  }

  /**
   * Format timestamp based on configuration
   * @returns {string} Formatted timestamp
   */
  formatTimestamp() {
    const now = new Date();
    if (this.config.timestamp_format === 'unix') {
      return now.getTime(); // Return number, not string
    }
    return now.toISOString();
  }

  /**
   * Format log message
   * @param {string} level - Log level
   * @param {string} message - Log message
   * @param {Object} meta - Additional metadata
   * @returns {string} Formatted log message
   */
  formatMessage(level, message, meta = {}) {
    const timestamp = this.formatTimestamp();
    let metaString = '';

    if (Object.keys(meta).length > 0) {
      try {
        metaString = JSON.stringify(meta);
      } catch (error) {
        if (error.message.includes('circular')) {
          // Handle circular references by creating a safe copy
          metaString = JSON.stringify(this.safeStringify(meta));
        } else {
          // Fallback for other JSON serialization errors
          metaString = JSON.stringify({
            error: 'Failed to serialize metadata',
            type: typeof meta,
            keys: Object.keys(meta)
          });
        }
      }

      // Check if any Error objects were serialized as empty objects
      if (metaString.includes('{}')) {
        // Use safeStringify to properly handle Error objects
        metaString = JSON.stringify(this.safeStringify(meta));
      }
    }

    return `[${timestamp}] [${level.toUpperCase()}] ${message} ${metaString}`.trim();
  }

  /**
   * Safely stringify object with circular reference handling
   * @param {any} obj - Object to stringify
   * @param {WeakSet} seen - Set of already seen objects (for recursion)
   * @returns {any} Safe representation of the object
   */
  safeStringify(obj, seen = new WeakSet()) {
    if (obj === null || obj === undefined) {
      return obj;
    }

    // Special handling for Error objects
    if (obj instanceof Error) {
      return {
        name: obj.name,
        message: obj.message,
        stack: obj.stack,
        code: obj.code,
        ...(obj.cause && { cause: obj.cause })
      };
    }

    if (typeof obj !== 'object') {
      return obj;
    }

    if (seen.has(obj)) {
      return '[Circular Reference]';
    }

    seen.add(obj);

    if (Array.isArray(obj)) {
      return obj.map(item => this.safeStringify(item, seen));
    }

    const result = {};
    for (const [key, value] of Object.entries(obj)) {
      try {
        result[key] = this.safeStringify(value, seen);
      } catch (error) {
        result[key] = `[Error: ${error.message}]`;
      }
    }

    return result;
  }

  /**
   * Write log message to console
   * @param {string} level - Log level
   * @param {string} message - Formatted message
   */
  writeConsole(level, message) {
    if (!this.config.console_enabled) return;

    if (this.config.colors && process.stdout.isTTY) {
      const color = this.colors[level] || this.colors.reset;
      console.log(`${color}${message}${this.colors.reset}`);
    } else {
      console.log(message);
    }
  }

  /**
   * Write log message to file
   * @param {string} message - Formatted message
   */
  async writeFile(message) {
    if (!this.config.file_enabled) return;

    try {
      // Ensure log directory exists
      const logDir = path.dirname(this.logFile);
      if (!fs.existsSync(logDir)) {
        fs.mkdirSync(logDir, { recursive: true });
      }

      // Check if file needs rotation
      if (await this.shouldRotateFile()) {
        await this.rotateLogFile();
      }

      // Append to log file
      await fsPromises.appendFile(this.logFile, message + '\n');
    } catch (error) {
      // If file logging fails, fall back to console
      console.error('Failed to write to log file:', error.message);
      this.writeConsole('error', `Log file write error: ${error.message}`);
    }
  }

  /**
   * Check if log file should be rotated
   * @returns {boolean} Whether rotation is needed
   */
  async shouldRotateFile() {
    try {
      if (!fs.existsSync(this.logFile)) {
        return false;
      }

      const stats = await fsPromises.stat(this.logFile);
      const maxSizeBytes = this.parseSize(this.config.max_file_size);

      return stats.size >= maxSizeBytes;
    } catch (error) {
      return false;
    }
  }

  /**
   * Rotate log file
   */
  async rotateLogFile() {
    try {
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const rotatedFile = this.logFile.replace('.log', `-${timestamp}.log`);

      if (fs.existsSync(this.logFile)) {
        await fsPromises.rename(this.logFile, rotatedFile);
      }

      // Clean up old log files
      await this.cleanupOldLogs();
    } catch (error) {
      console.error('Failed to rotate log file:', error.message);
    }
  }

  /**
   * Clean up old log files based on retention policy
   */
  async cleanupOldLogs() {
    try {
      const files = await fsPromises.readdir(this.logDir);
      const logFiles = files
        .filter(file => file.startsWith('vision-analyzer') && file.endsWith('.log'))
        .map(file => path.join(this.logDir, file));

      const cutoffTime = Date.now() - (this.config.retention_days * 24 * 60 * 60 * 1000);

      for (const logFile of logFiles) {
        try {
          const stats = await fsPromises.stat(logFile);
          if (stats.mtime.getTime() < cutoffTime) {
            await fsPromises.rm(logFile);
          }
        } catch (error) {
          // Skip files that can't be accessed
          continue;
        }
      }
    } catch (error) {
      console.error('Failed to cleanup old logs:', error.message);
    }
  }

  /**
   * Parse size string to bytes
   * @param {string} size - Size string (e.g., "10MB")
   * @returns {number} Size in bytes
   */
  parseSize(size) {
    const units = {
      'B': 1,
      'KB': 1024,
      'MB': 1024 * 1024,
      'GB': 1024 * 1024 * 1024
    };

    const match = size.match(/^(\d+)(B|KB|MB|GB)$/i);
    if (!match) {
      return 10 * 1024 * 1024; // Default to 10MB
    }

    const [, sizeStr, unit] = match;
    return parseInt(sizeStr, 10) * (units[unit.toUpperCase()] || 1);
  }

  /**
   * Generic log method
   * @param {string} level - Log level
   * @param {string} message - Log message
   * @param {Object} meta - Additional metadata
   */
  async log(level, message, meta = {}) {
    // Check if we should log at this level
    if (LOG_LEVELS[level] > this.currentLogLevel) {
      return;
    }

    const formattedMessage = this.formatMessage(level, message, meta);

    // Write to console
    this.writeConsole(level, formattedMessage);

    // Write to file asynchronously
    if (this.config.file_enabled) {
      // Don't await file logging to avoid blocking
      this.writeFile(formattedMessage).catch(() => {
        // Error already handled in writeFile
      });
    }
  }

  /**
   * Log error message
   * @param {string} message - Error message
   * @param {Object|Error} error - Error object or details
   */
  async error(message, error = {}) {
    let meta = {};

    if (error instanceof Error) {
      meta = {
        name: error.name,
        message: error.message,
        stack: error.stack,
        code: error.code,
        ...(error.toJSON && error.toJSON())
      };
    } else if (typeof error === 'object') {
      meta = error;
    }

    await this.log('error', message, meta);
  }

  /**
   * Log warning message
   * @param {string} message - Warning message
   * @param {Object} meta - Additional metadata
   */
  async warn(message, meta = {}) {
    await this.log('warn', message, meta);
  }

  /**
   * Log info message
   * @param {string} message - Info message
   * @param {Object} meta - Additional metadata
   */
  async info(message, meta = {}) {
    await this.log('info', message, meta);
  }

  /**
   * Log debug message
   * @param {string} message - Debug message
   * @param {Object} meta - Additional metadata
   */
  async debug(message, meta = {}) {
    await this.log('debug', message, meta);
  }

  /**
   * Update logger configuration
   * @param {Object} newConfig - New configuration
   */
  updateConfig(newConfig) {
    this.config = { ...this.config, ...newConfig };
    this.currentLogLevel = LOG_LEVELS[this.config.level] !== undefined ?
      LOG_LEVELS[this.config.level] : LOG_LEVELS.info;
  }

  /**
   * Get recent log entries
   * @param {number} lines - Number of lines to retrieve
   * @returns {Promise<string[]>} Array of log lines
   */
  async getRecentLogs(lines = 100) {
    try {
      if (!fs.existsSync(this.logFile)) {
        return [];
      }

      const content = await fsPromises.readFile(this.logFile, 'utf8');
      const allLines = content.trim().split('\n');

      return allLines.slice(-lines);
    } catch (error) {
      throw ErrorFactory.filesystem(`Failed to read log file: ${error.message}`, this.logFile, 'read');
    }
  }

  /**
   * Search log entries by pattern
   * @param {string} pattern - Search pattern (supports regex)
   * @param {number} limit - Maximum number of results
   * @returns {Promise<string[]>} Matching log lines
   */
  async searchLogs(pattern, limit = 50) {
    try {
      if (!fs.existsSync(this.logFile)) {
        return [];
      }

      const content = await fsPromises.readFile(this.logFile, 'utf8');
      const lines = content.trim().split('\n');

      const regex = new RegExp(pattern, 'i');
      const matches = lines.filter(line => regex.test(line));

      return matches.slice(-limit);
    } catch (error) {
      throw ErrorFactory.filesystem(`Failed to search log file: ${error.message}`, this.logFile, 'search');
    }
  }
}

/**
 * Logger factory for creating and managing logger instances
 */
class LoggerFactory {
  constructor() {
    this.instances = new Map();
  }

  /**
   * Get or create logger instance
   * @param {string} name - Logger name
   * @param {Object} config - Logger configuration
   * @returns {Logger} Logger instance
   */
  getInstance(name = 'default', config = {}) {
    if (!this.instances.has(name)) {
      this.instances.set(name, new Logger(config));
    }
    return this.instances.get(name);
  }

  /**
   * Update configuration for all logger instances
   * @param {Object} config - New configuration
   */
  updateAllConfigs(config) {
    for (const [name, logger] of this.instances) {
      logger.updateConfig(config);
    }
  }

  /**
   * Close all logger instances and cleanup resources
   */
  async closeAll() {
    // For file-based logging, ensure all pending writes are flushed
    const promises = Array.from(this.instances.values()).map(async (logger) => {
      // Give some time for async file writes to complete
      await new Promise(resolve => setTimeout(resolve, 100));
    });

    await Promise.all(promises);
    this.instances.clear();
  }
}

// Global logger factory instance
const loggerFactory = new LoggerFactory();

/**
 * Get default logger instance
 * @returns {Logger} Default logger
 */
function getLogger() {
  return loggerFactory.getInstance();
}

/**
 * Create named logger instance
 * @param {string} name - Logger name
 * @param {Object} config - Logger configuration
 * @returns {Logger} Named logger instance
 */
function createLogger(name, config) {
  return loggerFactory.getInstance(name, config);
}

/**
 * Update global logger configuration
 * @param {Object} config - New configuration
 */
function updateLoggerConfig(config) {
  loggerFactory.updateAllConfigs(config);
}

// Export classes and functions
module.exports = {
  Logger,
  LoggerFactory,
  getLogger,
  createLogger,
  updateLoggerConfig,
  LOG_LEVELS,
  DEFAULT_CONFIG
};